#include <asm/uaccess.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/slab.h>

/***************************************************************/
/************************** Debug ******************************/
/***************************************************************/
/*
 * Error messages
 */
#define print_failed_exit(func)					\
	pr_err_once("FAILED:%s()\n", func);
#define print_error(...)					\
	do {							\
		pr_err_once("ERROR:");				\
		printk(__VA_ARGS__);				\
		printk("\n");					\
	} while (0)
/*
 * LOG messages
 */
#define print_start(func)					\
	pr_debug("-->%s()\n", func);
#define print_exit(func)					\
	pr_debug("<--%s()\n", func);
#define print_log(...)						\
	do {							\
		pr_debug("LOG: ");				\
		pr_debug(__VA_ARGS__);				\
		pr_debug("\n");					\
	} while (0)

/***************************************************************/
/****************** Init/destroy functions *********************/
/***************************************************************/
static int __init stack_dev_init(void);
static void __exit stack_dev_exit(void);

/***************************************************************/
/************************ Misc device **************************/
/***************************************************************/
/*
 * File operations
 */
static ssize_t stack_dev_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t stack_dev_write(struct file *, const char __user *, size_t,
			       loff_t *);

static const struct file_operations stack_dev_fops = {
	.owner		= THIS_MODULE,
	.read		= stack_dev_read,
	.write		= stack_dev_write
};
/*
 * Device
 */
#define DEVICE_NAME		"stack"
static struct miscdevice stack_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &stack_dev_fops,
	.mode = 0666
};

/***************************************************************/
/*************************** Buffer ****************************/
/***************************************************************/
static struct list_head stack;
struct symbol {
	char c;
	struct list_head list;
};

/***************************************************************/
/*************************** Cache *****************************/
/***************************************************************/
static struct kmem_cache *stack_cache;
#define CACHE_NAME		"stack_cache"
#define CACHE_OBJ_SIZE		sizeof(struct symbol)

static int stack_add(char c);
static char stack_pop(void);

static int stack_add(char c)
{
	struct symbol *s = NULL;
	s = kmem_cache_zalloc(stack_cache, GFP_KERNEL);
	if (!s) {
		print_error("Couldn't allocate memory");
		print_failed_exit("stack_add");
		return -ENOMEM;
	}
	s->c = c;
	list_add(&(s->list), &stack);

	return 0;
}

static char stack_pop(void)
{
	struct symbol *s = NULL;
	char c;

	s = list_first_entry(&stack, struct symbol, list);
	list_del(&(s->list));
	c = s->c;
	kmem_cache_free(stack_cache, s);

	return c;
}

static ssize_t stack_dev_read(struct file *filp, char __user *buf, size_t size,
			  loff_t *off)
{
	char c;

	if (list_empty(&stack))
		return 0;

	c = stack_pop();

	if (copy_to_user(buf, &c, 1)) {
		print_error("Couldn't read byte from buffer");
		print_failed_exit("stack_dev_read");
		return -EFAULT;
	}

	*off += 1;
	return 1;
}

static ssize_t stack_dev_write(struct file *filp, const char __user *buf,
			       size_t size, loff_t *off)
{
	int err = 0;
	char c;

	if (copy_from_user(&c, buf, 1)) {
		print_error("Couldn't get byte from buffer");
		print_failed_exit("stack_dev_write");
		return -EFAULT;
	}

	err = stack_add(c);
	if (err != 0)
		return err;

	*off += 1;
	return 1;
}

static int __init stack_dev_init(void)
{
	int err = 0;
	print_start("stack_dev_init");

	err = misc_register(&stack_dev);
	if (err != 0) {
		print_error("Couldn't register misc device");
		print_failed_exit("stack_dev_init");
		return err;
	}

	INIT_LIST_HEAD(&stack);

	stack_cache = kmem_cache_create(CACHE_NAME, CACHE_OBJ_SIZE, 0,
					SLAB_HWCACHE_ALIGN, NULL);
	if (!stack_cache) {
		print_error("Couldn't create slab cache");
		print_failed_exit("stack_dev_init");
		return -ENOMEM;
	}

	print_exit("stack_dev_init");
	return 0;
}

static void __exit stack_dev_exit(void)
{
	print_start("stack_dev_exit");

	misc_deregister(&stack_dev);
	if (stack_cache)
		kmem_cache_destroy(stack_cache);

	print_exit("stack_dev_exit");
	return;
}

module_init(stack_dev_init);
module_exit(stack_dev_exit);

MODULE_AUTHOR("Petr Nechaev <petr@nechaev.us>");
MODULE_DESCRIPTION("Simple stack device");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0.1");
